﻿using System;
using System.Collections.Generic;
using MySql.Data.MySqlClient;
using System.Data;
using iRealtor.Maps.DataAccess.Dao.EntityDataProviders;
using iRealtor.Maps.DataAccess.Models;

namespace iRealtor.Maps.DataAccess.Dao
{
	public class MySqlDao : IDao, IMySqlCommandCreater
	{
		public MySqlDao(string connectionString)
		{
			ConnectionString = connectionString;
		}

		public string ConnectionString { get; private set; }

		#region IDao Members

		public void Save<TEntity>(TEntity entity) where TEntity : Entity
		{
			entity.SetDefaultValues();

			try
			{
				OpenConnection();
				EntityDataProvider<TEntity>.CreateProvider().Save(entity, this);
			}
			finally
			{
				CloseConnection();
			}
		}

		public TEntity Load<TEntity>(object key) where TEntity : Entity
		{
			TEntity res = null;

			if (key == null)
				throw new ArgumentNullException("key");

			try
			{
				OpenConnection();
				res = EntityDataProvider<TEntity>.CreateProvider().Load(key, this);
			}
			finally
			{
				CloseConnection();
			}

			return res;
		}

		public IList<TEntity> Load<TEntity>(MySqlCommand selectCommand) where TEntity : Entity
		{
			IList<TEntity> res = null;
			try
			{
				selectCommand.Transaction = currentTransaction;
				selectCommand.Connection = OpenConnection();
				res = EntityDataProvider<TEntity>.CreateProvider().Load(selectCommand);
			}
			finally
			{
				CloseConnection();
			}

			return res;
		}

		public Nullable<T> ExecuteScalar<T>(MySqlCommand selectCommand) where T : struct
		{
			object res = null;
			try
			{
				selectCommand.Transaction = currentTransaction;
				selectCommand.Connection = OpenConnection();
				res = selectCommand.ExecuteScalar();
			}
			finally
			{
				CloseConnection();
			}

			return (res != null) ? new Nullable<T>((T)Convert.ChangeType(res, typeof(T))) : null;
		}

		public void ExecuteNonQuery(MySqlCommand command)
		{
			try
			{
				command.Transaction = currentTransaction;
				command.Connection = OpenConnection();
				command.ExecuteNonQuery();
			}
			finally
			{
				CloseConnection();
			}
		}

		#endregion

		#region Connections and transactions

		private MySqlConnection currentConnection = null;

		private MySqlConnection OpenConnection()
		{
			if (currentConnection == null)
			{
				currentConnection = new MySqlConnection(ConnectionString);
				currentConnection.Open();
			}
			return currentConnection;
		}

		private void CloseConnection()
		{
			if (currentTransaction == null)
			{
				currentConnection.Close();
				currentConnection = null;
			}
		}

		private MySqlTransaction currentTransaction = null;

		private int transactionLevel = 0;

		public void BeginTransaction()
		{
			if (transactionLevel == 0)
			{
				MySqlConnection c = OpenConnection();
				currentTransaction = c.BeginTransaction();
			}
			transactionLevel++;
		}

		public void CommitTransaction()
		{
			transactionLevel--;
			if (transactionLevel == 0)
			{
				currentTransaction.Commit();
				currentTransaction = null;
				CloseConnection();
			}
		}

		public void RollbackTransaction()
		{
			transactionLevel--;
			if (transactionLevel == 0)
			{
				currentTransaction.Rollback();
				currentTransaction = null;
				CloseConnection();
			}
			else
			{
				throw new Exception("Inner transaction rollback");
			}
		}

		#endregion

		#region IMySqlCommandCreater Members

		public MySqlCommand CreateCommand()
		{
			MySqlCommand res = new MySqlCommand();
			res.CommandType = CommandType.Text;
			res.Connection = OpenConnection();
			res.Transaction = currentTransaction;
			return res;
		}

		public MySqlCommand CreateCommand(string sql)
		{
			MySqlCommand res = CreateCommand();
			res.CommandText = sql;
			return res;
		}

		#endregion
	}
}
